using System;
using SunflowSharp.Maths;

namespace SunflowSharp.Core
{

    /**
     * This class represents a ray as a oriented half line segment. The ray
     * direction is always normalized. The valid region is delimted by two distances
     * along the ray, tMin and tMax.
     */
    public class Ray
    {
        public float ox, oy, oz;
        public float dx, dy, dz;
        private float tMin;
        private float tMax;
        private static float EPSILON = 0;// 0.01f;

        private Ray()
        {
        }

        /**
         * Creates a new ray that points from the given origin to the given
         * direction. The ray has infinite Length. The direction vector is
         * normalized.
         * 
         * @param ox ray origin x
         * @param oy ray origin y
         * @param oz ray origin z
         * @param dx ray direction x
         * @param dy ray direction y
         * @param dz ray direction z
         */

        public Ray(float ox, float oy, float oz, float dx, float dy, float dz)
        {
            this.ox = ox;
            this.oy = oy;
            this.oz = oz;
            this.dx = dx;
            this.dy = dy;
            this.dz = dz;
            float inf = 1.0f / (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
            this.dx *= inf;
            this.dy *= inf;
            this.dz *= inf;
            tMin = EPSILON;
            tMax = float.PositiveInfinity;
        }

        /**
         * Creates a new ray that points from the given origin to the given
         * direction. The ray has infinite Length. The direction vector is
         * normalized.
         * 
         * @param o ray origin
         * @param d ray direction (need not be normalized)
         */
        public Ray(Point3 o, Vector3 d)
        {
            ox = o.x;
            oy = o.y;
            oz = o.z;
            dx = d.x;
            dy = d.y;
            dz = d.z;
            float inf = 1.0f / (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
            dx *= inf;
            dy *= inf;
            dz *= inf;
            tMin = EPSILON;
            tMax = float.PositiveInfinity;
        }

        /**
         * Creates a new ray that points from point a to point b. The created ray
         * will set tMin and tMax to limit the ray to the segment (a,b)
         * (non-inclusive of a and b). This is often used to create shadow rays.
         * 
         * @param a start point
         * @param b end point
         */
        public Ray(Point3 a, Point3 b)
        {
            ox = a.x;
            oy = a.y;
            oz = a.z;
            dx = b.x - ox;
            dy = b.y - oy;
            dz = b.z - oz;
            tMin = EPSILON;
            float n = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
            float inf = 1.0f / n;
            dx *= inf;
            dy *= inf;
            dz *= inf;
            tMax = n - EPSILON;
        }

        /**
         * Create a new ray by transforming the supplied one by the given matrix. If
         * the matrix is <code>null</code>, the original ray is returned.
         * 
         * @param m matrix to transform the ray by
         */
        public Ray transform(Matrix4 m)
        {
            if (m == null)
                return this;
            Ray r = new Ray();
            r.ox = m.transformPX(ox, oy, oz);
            r.oy = m.transformPY(ox, oy, oz);
            r.oz = m.transformPZ(ox, oy, oz);
            r.dx = m.transformVX(dx, dy, dz);
            r.dy = m.transformVY(dx, dy, dz);
            r.dz = m.transformVZ(dx, dy, dz);
            r.tMin = tMin;
            r.tMax = tMax;
            return r;
        }

        /**
         * Normalize the direction component of the ray.
         */
        public void normalize()
        {
            float inf = 1.0f / (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
            dx *= inf;
            dy *= inf;
            dz *= inf;
        }

        /**
         * Gets the minimum distance along the ray - usually 0.
         * 
         * @return value of the smallest distance along the ray
         */
        public float getMin()
        {
            return tMin;
        }

        /**
         * Gets the maximum distance along the ray. May be infinite.
         * 
         * @return value of the largest distance along the ray
         */
        public float getMax()
        {
            return tMax;
        }

        /**
         * Creates a vector to represent the direction of the ray.
         * 
         * @return a vector equal to the direction of this ray
         */
        public Vector3 getDirection()
        {
            return new Vector3(dx, dy, dz);
        }

        /**
         * Checks to see if the specified distance falls within the valid range on
         * this ray. This should always be used before an intersection with the ray
         * is detected.
         * 
         * @param t distance to be tested
         * @return <code>true</code> if t falls between the minimum and maximum
         *         distance of this ray, <code>false</code> otherwise
         */
        public bool isInside(float t)
        {
            return (tMin < t) && (t < tMax);
        }

        /**
         * Gets the end point of the ray. A reference to <code>dest</code> is
         * returned to support chaining.
         * 
         * @param dest reference to the point to store
         * @return reference to <code>dest</code>
         */
        public Point3 getPoint(Point3 dest)
        {
            dest.x = ox + (tMax * dx);
            dest.y = oy + (tMax * dy);
            dest.z = oz + (tMax * dz);
            return dest;
        }

        /**
         * Computes the dot product of an arbitrary vector with the direction of the
         * ray. This method avoids having to call getDirection() which would
         * instantiate a new Vector object.
         * 
         * @param v vector
         * @return dot product of the ray direction and the specified vector
         */
        public float dot(Vector3 v)
        {
            return dx * v.x + dy * v.y + dz * v.z;
        }

        /**
         * Computes the dot product of an arbitrary vector with the direction of the
         * ray. This method avoids having to call getDirection() which would
         * instantiate a new Vector object.
         * 
         * @param vx vector x coordinate
         * @param vy vector y coordinate
         * @param vz vector z coordinate
         * @return dot product of the ray direction and the specified vector
         */
        public float dot(float vx, float vy, float vz)
        {
            return dx * vx + dy * vy + dz * vz;
        }

        /**
         * Updates the maximum to the specified distance if and only if the new
         * distance is smaller than the current one.
         * 
         * @param t new maximum distance
         */
        public void setMax(float t)
        {
            tMax = t;
        }
    }
}